package com.basic.business.redis.server;

import com.basic.business.common.constants.Constants;
import com.basic.business.common.enums.PstStatus;
import com.basic.business.common.redis.RedisCacheUtils;
import com.basic.business.common.utils.StringUtils;
import com.basic.business.log.service.ITOrdersLogService;
import com.basic.business.order.domain.TOrdersDetail;
import com.basic.business.order.domain.TOrdersMain;
import com.basic.business.order.domain.TOrdersMerge;
import com.basic.business.redis.domain.TOrdersDetailRedis;
import com.basic.business.redis.domain.TOrdersMainRedis;
import com.basic.business.redis.domain.TOrdersMergeRedis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 订单信息同步到Redis中（优化版）
 */
@Slf4j
@Component
public class SyncOrdersToRedisServer {

    @Resource
    private ITOrdersLogService logService;

    /**
     * 入口方法
     */
    public void init(List<TOrdersMain> ordersMains) {
        try {
            ordersMains.forEach(this::processOrderMain);
        } catch (Exception e) {
            log.error("【订单信息同步到Redis中失败】", e);
        }
    }

    /**
     * 处理订单主信息，根据订单在Redis中的存在情况决定是创建新订单还是更新现有订单。
     *
     * @param ordersMain 订单主信息对象，包含订单的基本信息。
     */
    private void processOrderMain(TOrdersMain ordersMain) {
        try {
            // 生成Redis中存储订单的键值
            String redisKey = Constants.ORDERS_REDIS_KEY + ordersMain.getSid();

            // 从Redis中获取订单信息
            TOrdersMainRedis redisOrder = RedisCacheUtils.getCacheObject(redisKey);

            // 判断订单是否存在于Redis中
            if (redisOrder == null) {
                // 如果订单不存在于Redis中，则创建新订单
                createNewOrderInRedis(ordersMain, redisKey);
            } else {
                // 如果订单已存在于Redis中，则更新现有订单
                updateExistingOrder(ordersMain, redisOrder, redisKey);
            }
        } catch (Exception e) {
            log.error("【订单信息同步到Redis中失败】", e);
        }
    }

    /**
     * 创建新订单并将其存储到Redis中
     *
     * @param ordersMain 订单主表实体对象，包含订单的详细信息
     * @param redisKey Redis中存储订单的键值
     */
    private void createNewOrderInRedis(TOrdersMain ordersMain, String redisKey) {
        try {
            // 创建一个新的Redis订单对象
            TOrdersMainRedis newRedisOrder = new TOrdersMainRedis();

            // 将订单主表实体的属性复制到Redis订单对象中，并应用业务逻辑
            copyPropertiesWithBusinessLogic(ordersMain, newRedisOrder);

            // 将Redis订单对象存储到Redis缓存中
            RedisCacheUtils.setCacheObject(redisKey, newRedisOrder);
            // 记录订单同步日志
            logService.insert(ordersMain.getSid(), ordersMain.getSid(), "同步下载订单");
        } catch (Exception e) {
            log.error("【订单信息同步到Redis中失败】", e);
        }
    }

    /**
     * 更新现有订单
     *
     * 该函数用于比较并更新Redis中的订单信息。首先，它会检测当前订单与Redis中存储的订单之间的差异，
     * 如果有变化，则记录这些变化。然后，根据业务逻辑将当前订单的属性复制到Redis订单对象中，
     * 最后将更新后的订单对象重新存储到Redis中。
     *
     * @param ordersMain 当前订单对象，包含最新的订单信息
     * @param redisOrder Redis中存储的订单对象，将被更新
     * @param redisKey Redis中存储订单的键值，用于更新Redis中的订单对象
     */
    private void updateExistingOrder(TOrdersMain ordersMain, TOrdersMainRedis redisOrder, String redisKey) {
        try {
            // 检测当前订单与Redis中存储的订单之间的差异
            List<String> changes = detectChanges(ordersMain, redisOrder);

            // 如果有变化，则记录这些变化
            if (!changes.isEmpty()) {
                logChange(ordersMain, changes);
            }

            // 根据业务逻辑将当前订单的属性复制到Redis订单对象中
            copyPropertiesWithBusinessLogic(ordersMain, redisOrder);

            // 将更新后的订单对象重新存储到Redis中
            RedisCacheUtils.setCacheObject(redisKey, redisOrder);
        } catch (Exception e) {
            log.error("【订单信息同步到Redis中失败】", e);
        }
    }

    /**
     * 检测源订单对象与目标订单对象之间的变化，并返回变化列表。
     *
     * 该函数通过比较源订单对象（source）和目标订单对象（target）的主订单信息和订单明细信息，
     * 检测出两者之间的差异，并将这些差异以字符串列表的形式返回。
     *
     * @param source 源订单对象，包含待比较的订单信息。
     * @param target 目标订单对象，包含待比较的订单信息。
     * @return 返回一个字符串列表，包含检测到的所有变化信息。
     */
    private List<String> detectChanges(TOrdersMain source, TOrdersMainRedis target) {
        List<String> changes = new ArrayList<>();

        // 检测主订单信息的变化，并将变化添加到changes列表中
        detectMainOrderChanges(source, target, changes);

        // 检测订单明细信息的变化，并将变化添加到changes列表中
        detectDetailChanges(source, target, changes);

        return changes;
    }


    /**
     * 检测主订单信息的变化，并将变化记录到指定的列表中。
     *
     * 该函数比较源主订单对象和目标主订单对象的运单号（outSid），如果两者不一致，
     * 则将变化信息格式化为字符串并添加到变化列表中。
     *
     * @param source 源主订单对象，包含最新的订单信息。
     * @param target 目标主订单对象，通常是从Redis中获取的订单信息。
     * @param changes 用于存储检测到的变化信息的列表，每个变化信息以字符串形式存储。
     */
    private void detectMainOrderChanges(TOrdersMain source, TOrdersMainRedis target, List<String> changes) {
        if (target == null) {
            changes.add(String.format("运单号【空】→【%s】", source.getOutSid()));
            return;
        }
        // 比较源和目标对象的运单号，如果不一致，则记录变化信息
        if (!StringUtils.equals(source.getOutSid(), target.getOutSid())) {
            changes.add(String.format("运单号【%s】→【%s】", target.getOutSid(), source.getOutSid()));
        }
    }


    /**
     * 检测订单明细的变化，并将变化记录到指定的列表中。
     *
     * @param source 源订单对象，包含需要比较的订单明细列表。
     * @param target 目标订单对象，包含需要比较的订单明细列表。
     * @param changes 用于记录变化的列表，将存储检测到的变化信息。
     */
    private void detectDetailChanges(TOrdersMain source, TOrdersMainRedis target, List<String> changes) {
        try {
            // 将目标订单的明细列表转换为以ID为键的Map，方便后续查找
            Map<Long, TOrdersDetailRedis> targetDetailsMap = target.getDetailList().stream()
                    .collect(Collectors.toMap(TOrdersDetailRedis::getId, d -> d));

            // 遍历源订单的明细列表，过滤掉礼品项，并与目标订单的明细进行比较
            source.getDetailList().stream()
                    .filter(detail -> "N".equals(detail.getIsGift()))
                    .forEach(sourceDetail -> {
                        TOrdersDetailRedis targetDetail = targetDetailsMap.get(sourceDetail.getId());
                        if (targetDetail != null) {
                            // 检查源明细和目标明细之间的变化，并将变化记录到changes列表中
                            checkDetailChanges(sourceDetail, targetDetail, changes);
                        }
                    });

            // 获取目标订单中的合并订单，并与目标合并订单的备注进行比较
            Map<Long, TOrdersMergeRedis> targetMergesMap = target.getMergeList().stream()
                    .collect(Collectors.toMap(TOrdersMergeRedis::getMergeSid, m -> m));

            source.getMergeList().forEach(sourceMerge -> {
                TOrdersMergeRedis targetMerge = targetMergesMap.get(sourceMerge.getMergeSid());
                checkMergeChanges(sourceMerge, targetMerge, changes);
            });
        } catch (Exception e) {
            log.error("【订单信息同步到Redis中失败】", e);
        }
    }

    /**
     * 检测订单明细变化
     *
     * 该函数用于比较源订单明细（source）和目标订单明细（target）之间的差异，并将变化记录到changes列表中。
     *
     * @param source 源订单明细对象，包含原始的订单明细信息
     * @param target 目标订单明细对象，通常是从Redis中获取的订单明细信息
     * @param changes 用于存储变化的列表，每个变化以字符串形式记录
     */
    private void checkDetailChanges(TOrdersDetail source, TOrdersDetailRedis target, List<String> changes) {
        // 比较并记录系统状态的变化
        if (!StringUtils.equals(source.getSysStatus(), target.getSysStatus())) {
            changes.add(String.format("状态【%s】→【%s】",
                    target.getSysStatus(), source.getSysStatus()));
        }
    }

    private void checkMergeChanges(TOrdersMerge source, TOrdersMergeRedis target, List<String> changes) {
        // 如果target为空，则是原来没有备注，现在source中有的话
        if (target == null && source.getSellerMemo() != null) {
            changes.add(String.format("备注【无】→【%s】", source.getSellerMemo()));
            return;
        }
        // 比较并记录备注的变化
        if (target != null && !StringUtils.equals(source.getSellerMemo(), target.getSellerMemo())) {
            changes.add(String.format("备注【%s】→【%s】",
                    target.getSellerMemo() == null ? "无" : target.getSellerMemo(), source.getSellerMemo()));
        }
    }

    /**
     * 记录订单信息变更日志（为每个非赠品明细单独记录）
     *
     * 该函数用于将订单的变更信息记录到日志中，为每个非赠品明细单独插入日志。
     *
     * @param ordersMain 订单主信息对象
     * @param changes 订单的变更信息列表
     */
    private void logChange(TOrdersMain ordersMain, List<String> changes) {
        // 将变更信息列表拼接为日志内容字符串
        String logContent = "同步修改订单信息：" + String.join("; ", changes);

        logForEachDetail(ordersMain, logContent);
    }

    /**
     * 为每个非赠品明细记录日志
     *
     * @param ordersMain 订单主信息对象
     * @param logContent 日志内容
     */
    private void logForEachDetail(TOrdersMain ordersMain, String logContent) {
        // 获取所有非赠品明细
        List<TOrdersDetail> nonGiftDetails = ordersMain.getDetailList().stream()
                .filter(detail -> "N".equals(detail.getIsGift()))
                .collect(Collectors.toList());

        if (nonGiftDetails.isEmpty()) {
            return;
        }

        // 按 detailSid 分组去重（确保每个 detailSid 只记录一次）
        Map<Long, List<TOrdersDetail>> groupedBySid = nonGiftDetails.stream()
                .collect(Collectors.groupingBy(TOrdersDetail::getDetailSid));

        // 为每个唯一的 detailSid 记录一条日志
        groupedBySid.keySet().forEach(detailSid -> {
            logService.insert(ordersMain.getSid(), detailSid, logContent);
        });
    }

    /**
     * 将源对象 `TOrdersMain` 的属性复制到目标对象 `TOrdersMainRedis` 中，并在复制过程中应用特定的业务逻辑。
     *
     * 该函数首先使用 `BeanUtils.copyProperties` 方法复制源对象的基本属性到目标对象中，
     * 然后分别处理订单明细和合并订单的逻辑。
     *
     * @param source 源对象，包含需要复制的订单信息。
     * @param target 目标对象，用于接收复制后的订单信息。
     */
    private void copyPropertiesWithBusinessLogic(TOrdersMain source, TOrdersMainRedis target) {
        // 复制基本属性
        BeanUtils.copyProperties(source, target);

        // 处理订单明细
        processOrderDetails(source, target);

        // 处理合并订单（全量替换）
        processMergedOrders(source, target);

    }


    /**
     * 处理订单明细数据，将源订单明细数据同步到目标订单明细数据中。
     *
     * 该函数的主要功能包括：
     * 1. 从源订单中过滤出非赠品明细，并将其转换为Map以便快速查找。
     * 2. 准备目标订单的明细列表，如果目标订单的明细列表为空，则初始化为空列表。
     * 3. 移除目标订单中不存在于源订单的明细。
     * 4. 更新或新增目标订单中的明细，确保与源订单的明细一致。
     *
     * @param source 源订单对象，包含需要同步的订单明细数据。
     * @param target 目标订单对象，用于存储同步后的订单明细数据。
     */
    private void processOrderDetails(TOrdersMain source, TOrdersMainRedis target) {
        try {
            // 从源订单中过滤出非赠品明细，并将其转换为Map以便快速查找
            Map<Long, TOrdersDetail> sourceDetailsMap = source.getDetailList().stream()
                    .filter(d -> "N".equals(d.getIsGift()))
                    .collect(Collectors.toMap(TOrdersDetail::getId, d -> d));

            // 准备目标订单的明细列表，如果目标订单的明细列表为空，则初始化为空列表
            List<TOrdersDetailRedis> targetDetails = Optional.ofNullable(target.getDetailList())
                    .orElse(new ArrayList<>());

            // 移除目标订单中不存在于源订单的明细
            targetDetails.removeIf(d -> !sourceDetailsMap.containsKey(d.getId()));

            // 更新或新增目标订单中的明细，确保与源订单的明细一致
            sourceDetailsMap.forEach((id, sourceDetail) -> {
                Optional<TOrdersDetailRedis> targetDetailOpt = targetDetails.stream()
                        .filter(d -> d.getId() == id)
                        .findFirst();

                if (targetDetailOpt.isPresent()) {
                    updateExistingDetail(sourceDetail, targetDetailOpt.get());
                } else {
                    addNewDetail(sourceDetail, targetDetails);
                }
            });

            // 将处理后的明细列表设置回目标订单对象
            target.setDetailList(targetDetails);
        } catch (Exception e) {
            log.error("处理订单明细数据时发生异常：{}", e.getMessage(), e);
        }
    }


    /**
     * 更新现有的订单详情信息。
     * 该函数将源订单详情对象中的属性复制到目标订单详情对象中，但保持目标对象的自动处理状态不变。
     *
     * @param source 源订单详情对象，包含需要更新的属性值。
     * @param target 目标订单详情对象，将被更新但保持其自动处理状态。
     */
    private void updateExistingDetail(TOrdersDetail source, TOrdersDetailRedis target) {
        // 保存目标对象的原始自动处理状态
        String originalAutoProcessed = target.getPst();

        // 将源对象的属性复制到目标对象中
        BeanUtils.copyProperties(source, target);

        // 恢复目标对象的自动处理状态，确保其不被源对象覆盖
        target.setPst(originalAutoProcessed);
    }


    /**
     * 将源订单明细对象转换为Redis订单明细对象，并添加到目标列表中。
     *
     * @param source 源订单明细对象，包含需要复制的属性。
     * @param targetDetails 目标订单明细列表，用于存储转换后的Redis订单明细对象。
     */
    private void addNewDetail(TOrdersDetail source, List<TOrdersDetailRedis> targetDetails) {
        try {
            // 创建新的Redis订单明细对象，并复制源对象的属性
            TOrdersDetailRedis newDetail = new TOrdersDetailRedis();
            BeanUtils.copyProperties(source, newDetail);

            // 设置新明细的自动处理状态为未处理
            newDetail.setPst(PstStatus.UNPROCESSED.name());

            // 将新明细添加到目标列表中
            targetDetails.add(newDetail);
        } catch (Exception e) {
            log.error("将源订单明细对象转换为Redis订单明细对象时发生异常：{}", e.getMessage(), e);
        }
    }

    /**
     * 处理合并订单信息，将源对象中的合并订单列表复制到目标对象中。
     *
     * @param source 源订单主对象，包含需要处理的合并订单列表。
     * @param target 目标订单主对象，用于存储处理后的合并订单列表。
     */
    private void processMergedOrders(TOrdersMain source, TOrdersMainRedis target) {
        try {
            // 将源对象中的合并订单列表转换为目标对象中的合并订单列表
            List<TOrdersMergeRedis> mergedOrders = source.getMergeList().stream()
                    .map(merge -> {
                        // 创建新合并订单对象，并将源对象的属性复制到新对象中
                        TOrdersMergeRedis newMerge = new TOrdersMergeRedis();
                        BeanUtils.copyProperties(merge, newMerge);
                        return newMerge;
                    })
                    .collect(Collectors.toList());

            // 将处理后的合并订单列表设置到目标对象中
            target.setMergeList(mergedOrders);
        } catch (Exception e) {
            log.error("处理合并订单信息时发生异常：{}", e.getMessage(), e);
        }
    }

}